/* 日志落地
*  1.日志落地抽象类
*  2.向标准输出 输出日志类
*  3.向文件输出日志类
*  4.向文件滚动输出类(按大小/天数)
*  5.简单工厂生产日志落地类
*/

#ifndef _SINK_H_
#define _SINK_H_

#include <iostream>
#include <fstream>
#include <sstream>
#include <memory>
#include <string>
#include <cassert>
#include "util.hpp"

namespace Log
{
    //日志落地抽象类
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        virtual ~LogSink() {}
        virtual void log(const char *data, size_t len)  = 0;

    };

    //日志标准输出落地
    class StdoutSink : public LogSink
    {
    public:
        void log(const char *data, size_t len) override
        {
            //因为cout<< 无法控制输出的字节数 所以调用重载函数进行输出
            std::cout.write(data,len);
        }
    };

    //日志向文件输出落地
    class FileSink : public LogSink
    {
    public:
        //将文件路径记录起来 建立句柄(IO)管理
        FileSink(const std::string& pathname):_pathname(pathname) 
        {
            //1.创建文件所在目录 - 获取文件路径 创建对应目录
            LogUtil::File::createDirectory(LogUtil::File::path(_pathname));
            //2.创建和打开文件
            _ofs.open(_pathname,std::ios::binary | std::ios::app); //以二进制打开 并追加写入
            assert(_ofs.is_open()); //验证是否成功打开文件
        }

        void log(const char *data, size_t len) override
        {
            _ofs.write(data,len);
            assert(_ofs.good()); //写入后检查句柄是否还正常 如果不正常则写入失效
        }

    private:
        std::string _pathname; //文件路径
        std::ofstream _ofs; //标准文件流
    };

    //日志向滚动文件输出落地
    class RollBySizeSink : public LogSink
    {
    public:
        //将文件路径记录起来 建立句柄(IO)管理
        RollBySizeSink(const std::string& basename,size_t max_fsize):_basename(basename),_max_fsize(max_fsize),_cur_fsize(0),_count(0)
        {
            //1.创建文件所在目录 - 获取文件路径 创建对应目录
            std::string pathname = createNewFile();
            LogUtil::File::createDirectory(LogUtil::File::path(pathname));
            //2.创建和打开文件
            _ofs.open(pathname,std::ios::binary | std::ios::app); //以二进制打开 并追加写入
            assert(_ofs.is_open()); //验证是否成功打开文件
        }

        void log(const char *data, size_t len) override
        {
            //文件写入满了 先关闭当前文件 再创建新文件
            if(_cur_fsize + len >= _max_fsize)
            {
                _ofs.close();
                //创建新文件名
                std::string pathname = createNewFile();
                //创建新文件
                _ofs.open(pathname,std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_fsize = 0;
            }
            _ofs.write(data,len);
            assert(_ofs.good()); //写入后检查句柄是否还正常 如果不正常则写入失效
            _cur_fsize += len;
        }

    private:
        //进行文件大小判断 如果文件写到规定大小则立刻按时间生成文件名生成新文件
        std::string createNewFile()
        {
            time_t t = LogUtil::Date::now();
            struct tm nowt;
            localtime_r(&t,&nowt);
            std::stringstream ss;
            ss << _basename;
            ss << nowt.tm_yday + 1900; //年 系统从1900开始计时
            ss << nowt.tm_mon + 1; //月
            ss << nowt.tm_mday; //日
            ss << nowt.tm_hour; //时
            ss << nowt.tm_min; //分
            ss << nowt.tm_sec; //秒
            ss << "-"; //添加当前创建文件数后缀
            ss << _count++; //区别于其他文件 防止一秒创建相同的文件导致向同一个文件中写入
            ss << ".log"; //后缀
            return ss.str();
        }
    private:
        //通过 基础文件名+扩展文件名(以时间为扩展文件名)组成一个实际的当前输出的文件名
        std::string _basename; //文件路径 例如:./log/base-20030314202020.log(或定义一个计数器 生成 base-1.log ...)
        std::ofstream _ofs; //标准文件流
        size_t _max_fsize; //文件可写入的最大字节数
        size_t _cur_fsize; //当前已经向文件写入的字节数
        int _count; //对创建的文件进行计数
    };

    class SinkFactor
    {
    public:
        //向函数传入可变参数包
        template<class SinkType,class ...Args> //静态不定参函数
        static LogSink::ptr create(Args&& ...args)
        {
            //参数在需要参数的对象构造中展开
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif